#include "SpaceFortress.h"
#include "InputManager.h"

#include <assert.h>

InputManager::InputManager()
{
	// Initialize the output data.
	_data.size = 0;
	_data.capacity = 0;
	_data.buffer = NULL;

	_data.entity = NULL;
	_data.storage = NULL;
	_data.max_storage = NULL;
	_data.rate = NULL;
	_data.ratio = NULL;
	_data.comodity_index = NULL;
	_data.max_range = NULL;
	_data.min_range = NULL;
}

InputManager::~InputManager()
{
	// De-initialize the output data.
	if (_data.capacity) {
		delete[] _data.buffer;
	}
	_data.buffer = NULL;

	_data.entity = NULL;
	_data.storage = NULL;
	_data.max_storage = NULL;
	_data.rate = NULL;
	_data.ratio = NULL;
	_data.comodity_index = NULL;
	_data.max_range = NULL;
	_data.min_range = NULL;
}

void InputManager::Allocate(const uint32 capacity)
{
	assert(capacity >_data.capacity);

	InstanceData new_data;
	const uint32 bytes = capacity * (sizeof(Entity) + 4 * sizeof(float) + 3 * sizeof(uint32));
	new_data.buffer = new unsigned char[bytes];
	new_data.size = _data.size;
	new_data.capacity = capacity;

	new_data.entity = (Entity*)(new_data.buffer);
	new_data.storage = (float*)(new_data.entity + capacity);
	new_data.max_storage = (float*)(new_data.storage + capacity);
	new_data.rate = (float*)(new_data.max_storage + 2 * capacity);
	new_data.ratio = (float*)(new_data.rate + capacity);
	new_data.comodity_index = (uint32*)(new_data.ratio + capacity);
	new_data.max_range = (uint32*)(new_data.comodity_index + capacity);
	new_data.min_range = (uint32*)(new_data.max_range + capacity);

	memcpy(new_data.entity, _data.entity, _data.size * sizeof(Entity));
	memcpy(new_data.storage, _data.storage, _data.size * sizeof(float));
	memcpy(new_data.max_storage, _data.max_storage, _data.size * sizeof(float));
	memcpy(new_data.rate, _data.rate, _data.size * sizeof(float));
	memcpy(new_data.ratio, _data.ratio, _data.size * sizeof(float));
	memcpy(new_data.comodity_index, _data.comodity_index, _data.size * sizeof(uint32));
	memcpy(new_data.max_range, _data.max_range, _data.size * sizeof(uint32));
	memcpy(new_data.min_range, _data.min_range, _data.size * sizeof(uint32));

	delete[] _data.buffer;
	_data = new_data;
}

void InputManager::SortData(const uint32 start, const uint32 stop)
{
	int32 i = start, j = stop;
	uint32 pivot = _data.entity[(start + stop) / 2].index();

	while (i <= j) {
		while (_data.entity[i].index() < pivot) {
			i++;
		}
		while (_data.entity[j].index() > pivot) {
			j--;
		}
		if (i <= j) {
			Entity temp_entity = _data.entity[i];
			float temp_storage = _data.storage[i];
			float temp_max_storage = _data.max_storage[i];
			float temp_rate = _data.rate[i];
			float temp_ratio = _data.ratio[i];
			uint32 temp_comodity_index = _data.comodity_index[i];
			uint32 temp_max_range = _data.max_range[i];
			uint32 temp_min_range = _data.min_range[i];

			_data.entity[i] = _data.entity[j];
			_data.storage[i] = _data.storage[j];
			_data.max_storage[i] = _data.max_storage[j];
			_data.rate[i] = _data.rate[j];
			_data.ratio[i] = _data.ratio[j];
			_data.comodity_index[i] = _data.comodity_index[j];
			_data.max_range[i] = _data.max_range[j];
			_data.min_range[i] = _data.min_range[j];

			_data.entity[j] = temp_entity;
			_data.storage[j] = temp_storage;
			_data.max_storage[j] = temp_max_storage;
			_data.rate[j] = temp_rate;
			_data.ratio[j] = temp_ratio;
			_data.comodity_index[j] = temp_comodity_index;
			_data.max_range[j] = temp_max_range;
			_data.min_range[j] = temp_min_range;

			i++;
			j--;
		}
	}

	if (start < j) {
		SortData(start, j);
	}
	if (i < stop) {
		SortData(i, stop);
	}
}

void InputManager::CalculateInputRatio()
{
	// Calculate the ratios.
	for (uint32 i = 0; i < _data.size; i++) {
		float ratio = _data.storage[i] / _data.rate[i];
		_data.ratio[i] = (ratio > 1.f) ? 1.f : ratio;
	}
}

// I'm going to assume that the entities are sorted ascending.
// This is a todo for later, make sure everything stores entities ascending in their _data. 
// - this means that the parameter entities will also be sorted since its created from the output entities
void InputManager::FindMinProduction(const uint32 output_count, const Entity* const entities, float* ratios)
{
	uint32 i = 0, j = 0;
	while (i < _data.size && j < output_count) {
		if (_data.entity[i].index() < entities[j].index()) {
			i++;
		} else if (_data.entity[i].index() > entities[j].index()) {
			j++;
		} else { // equal
			if (ratios[j] > _data.ratio[i]) {
				ratios[j] = _data.ratio[i];
			}
		}
	}
}

void InputManager::OverrideRatio(const uint32 entity_count, const Entity* const entities, float* ratios)
{
	uint32 i = 0, j = 0;
	while (i < _data.size && j < entity_count) {
		if (_data.entity[i].index() < entities[j].index()) {
			i++;
		} else if (_data.entity[i].index() > entities[j].index()) {
			j++;
		} else { // equal
			_data.ratio[i] = ratios[j];
		}
	}
}

void InputManager::ConsumeInput()
{
	for (uint32 i = 0; i < _data.size; i++) {
		float new_amount = _data.ratio[i] * _data.rate[i];
		_data.storage[i] -= new_amount;
	}
}

void InputManager::PlaceMarketBuys(BidData* bids) const
{
	for (uint32 i = 0; i < _data.size; i++) {
		const uint32 comodity_idx = _data.comodity_index[i];
		BidData& bd = bids[comodity_idx];
		const uint32 idx = bd.size++;

		uint32 range = _data.max_range[i] - _data.min_range[i];
		float favorability = (float)(bd.price[idx] - _data.max_range[i]);
		favorability /= (float)range;
		favorability = (float)_data.max_range[i] - favorability;

		bd.entity[idx] = _data.entity[i];
		bd.quantity[idx] = (_data.max_storage[i] - _data.storage[i]) * favorability;
		bd.price[idx] = rand() % range + _data.min_range[i];
		bd.storage_index[idx] = i;
	}
}

void InputManager::RegisterBuyResults(const BidData* const bids, const float* const comodity_mean, const float* const comodity_supply, const float* const comodity_demand, const uint32 num_resources)
{
	for (uint32 resource = 0; resource < num_resources; resource++) {
		const BidData& bd = bids[resource];

		for (uint32 i = 0; i < bd.size; i++) {
			const uint32 idx = bd.storage_index[i];

			if (bd.quantity_traded[i]) {
				_data.storage[idx] += bd.quantity_traded[i];

				const float mean_price = comodity_mean[_data.comodity_index[idx]];
				const int32 avg_price = bd.clearing_price[i] / bd.quantity_traded[i];

				if (avg_price > _data.max_range[idx]) {
					_data.max_range[idx] = avg_price;
				}
				else if (avg_price < _data.min_range[idx]) {
					_data.min_range[idx] = avg_price;
				}
				else {
					//Tighten the ranges around the mean by 5%
					_data.max_range[idx] -= (_data.max_range[idx] - mean_price) * 0.05f;
					_data.min_range[idx] += (mean_price - _data.min_range[idx]) * 0.05f;
				}
			}
			else {
				// failed to complete trade order.
				// If input is 10% full buy at a much higher price
				if (_data.storage[idx] / _data.max_storage[idx] > 0.9f) {
					_data.max_range[idx] *= 1.5f; // Increase maximum bound by 50%
					_data.min_range[idx] *= 1.5f; // Increase minimum bound by 50%
					continue;
				} // else
				// look at supply and demand and try to predict future trends.
				const float sd_ratio = comodity_demand[i] / comodity_supply[i];
				_data.max_range[idx] *= sd_ratio;
				_data.min_range[idx] *= sd_ratio;
			}
		}
	}
}

void InputManager::RemoveInputData(const OpenVector<Entity>& defunct_entities, OpenVector<uint32>& defunct_buyers)
{
	const uint32 entity_count = defunct_entities.size;
	uint32 shift_count = 0;
	uint32 i = 0, j = 0;

	// It may be worth while to match indexes to the first defunct entity.
	while (i < _data.size && j < entity_count) {
		if (_data.entity[i].index() < defunct_entities.data[j].index()) {

			_data.entity[i - shift_count] = _data.entity[i];
			_data.storage[i - shift_count] = _data.storage[i];
			_data.max_storage[i - shift_count] = _data.max_storage[i];
			_data.rate[i - shift_count] = _data.rate[i];
			_data.ratio[i - shift_count] = _data.ratio[i];
			_data.comodity_index[i - shift_count] = _data.comodity_index[i];
			_data.max_range[i - shift_count] = _data.max_range[i];
			_data.min_range[i - shift_count] = _data.min_range[i];

			i++;
		}
		else if (_data.entity[i].index() > defunct_entities.data[j].index()) {
			j++;
		}
		else { // equal.
			defunct_buyers.Add(_data.comodity_index[i]);

			shift_count++;
			i++;
		}
	}

	_data.size -= entity_count;
}

uint32 InputManager::AllocateStorage(const Entity* const entities, const uint32 num_entities, 
	const uint32* const comodity_indexes, const float* const comodity_rates, const uint32 num_inputs, const float* const mean_prices)
{
	uint32 cost = 0;

	if (_data.size + num_inputs * num_entities) {
		Allocate(_data.size + num_inputs * num_entities);
	}

	for (uint32 i = 0; i < num_entities; i++) {
		cost = 0; // This recalculates every iteration for no reason.
		for (uint32 j = 0; j < num_inputs; j++) {
			const uint32 idx = _data.size++;

			_data.entity[idx] = entities[i];
			_data.storage[idx] = 0.f;
			_data.max_storage[idx] = 20.f; // Arbitrary value.
			_data.rate[idx] = comodity_rates[j];
			_data.ratio[idx] = 0.f; // This gets re-calculated every tick.

			const uint32 com_idx = comodity_indexes[j];
			_data.comodity_index[idx] = com_idx;

			// Labor costs need to be factored in to this.
			float mean = mean_prices[com_idx];
			if (mean < 0.001f) {
				mean = 5; // arbitrary value. labor costs will replace this.
			}

			cost += (uint32)mean;

			// 60% range centered around the mean.
			_data.max_range[idx] = 1.3 * mean;
			_data.min_range[idx] = 0.7 * mean;
		}
	}

	// quicksort probably isnt the best option since the array is already mostly sorted.
	SortData(0, _data.size);

	return cost;
}